Skip to content

Un-deprecate getdata(); have it return an ImageLinearAccess object - #9883

Open
akx wants to merge 2 commits into
python-pillow:mainfrom
akx:image-getdata-linear-access
Open

Un-deprecate getdata(); have it return an ImageLinearAccess object#9883
akx wants to merge 2 commits into
python-pillow:mainfrom
akx:image-getdata-linear-access

Conversation

@akx

@akx akx commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Follows up on #9292 to un-deprecate getdata(), since get_flattened_data() is not a perfect replacement for linear access to image pixels given its upfront memory and time costs. (See #9292 (comment) for rationales.)

Fixes #9261 (since getdata now returns a real, well-typed iterator/access object). Given x.py like

# pyright: strict
from __future__ import annotations
from PIL import Image
im = Image.new("RGB", (4, 4))
printed = list(im.getdata())

pyright fails on main:

(main) $ uvx --quiet --with pytest --with numpy pyright@1.1.411 x.py
  x.py:5:1 - error: Type of "printed" is partially unknown
    Type of "printed" is "list[Unknown]" (reportUnknownVariableType)
  x.py:5:16 - error: Argument of type "ImagingCore" cannot be assigned to parameter "iterable" of type "Iterable[_T@list]" in function "__init__"
    "ImagingCore" is incompatible with protocol "Iterable[_T@list]"
      "__iter__" is not present (reportArgumentType)
2 errors, 0 warnings, 0 informations

but is fine on this branch:

(image-getdata-linear-access) $ uvx --quiet --with pytest --with numpy pyright@1.1.411 x.py
0 errors, 0 warnings, 0 informations

The optimizations from #9881 still help this too and don't conflict here.

@akx

akx commented Aug 21, 2026

Copy link
Copy Markdown
Contributor Author

Didn't think I'd bump into a PyPy bug implementing this, but here we are... pypy/pypy#5526

@akx
akx force-pushed the image-getdata-linear-access branch 2 times, most recently from ecf4175 to 3756d89 Compare August 21, 2026 12:29
@akx
akx marked this pull request as ready for review August 21, 2026 12:41
@radarhere

Copy link
Copy Markdown
Member

Could you identify exactly what workaround you added for that, so that one day when the PyPy fix has propagated, we are able to remove it?


def test_getdata_does_not_expose_the_image_core() -> None:
hopper_data = hopper().getdata()
# "Weird" core bits are not exposed:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean by 'weird'?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weird, as in things that a normal user shouldn't be touching (which was the thing in the first place). I can remove the comment if it's too weird in itself. :D

@akx

akx commented Aug 23, 2026

Copy link
Copy Markdown
Contributor Author

Could you identify exactly what workaround you added for that, so that one day when the PyPy fix has propagated, we are able to remove it?

The workaround is the linear access type also act as a mapping (.tp_as_mapping = &linear_access_as_mapping), since CPython's and PyPy's mechanisms prefer that for getitem when available. When the object does not act like a mapping, CPython and PyPy will use the sequence item accessor (linear_access_item) instead, but before pypy/pypy@e6ed5cc is shipped in PyPy, it doesn't do the negative-index adjustment like CPython does.

IOW, there's nothing that necessarily needs to be removed.

^^^^^^^^^^^^^^^

:py:meth:`~PIL.Image.Image.getdata` had been marked deprecated since 12.1.0,
to be removed in Pillow 14, as it returned a poorly specified internal Pillow data type.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
to be removed in Pillow 14, as it returned a poorly specified internal Pillow data type.
to be removed in Pillow 14, as it returned an internal Pillow data type.

I don't think it was poorly specified. Perhaps poorly documented, but by design, because it's... internal.

@radarhere

Copy link
Copy Markdown
Member

I'm reluctant to return a C object from our Python API, but you could quite rightly say that load() does the same thing with core.PixelAccess, and that this is similar functionality.

#9292 (comment)

My idea would be to return a Python class, rather than C, that calls getpixel() from a magic method. However, I recognise that you are likely to prefer C for performance reasons.

If tuple(self.im) is truly terrible for performance, then I'd suggest adjusting get_flattened_data to use tobytes() instead.

I'm not yet convinced that there is a common need for accessing lazy data that can't be suitably addressed with getpixel(). An example given in the other PR was

zip(im1.getdata(), im2.getdata()) to compare two images pixel by pixel and early-exit upon the first difference. With get_flattened_data(), you'd pay upfront to materialize all pixels twice.

but that sounds like something for us to do internally within __eq__(), not something to leave for users to find the most optimal route themselves.

@akx
akx force-pushed the image-getdata-linear-access branch from 2348fb6 to 5896568 Compare September 13, 2026 19:42
@akx

akx commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

I'm reluctant to return a C object from our Python API,

I understand that to a degree, but if/when said C object is tightly specified, I'm not sure I totally see the issue.

If tuple(self.im) is truly terrible for performance, then I'd suggest adjusting get_flattened_data to use tobytes() instead.

Both tuple(self.im) and get_flattened_data() using tobytes() have the same up-front memory bloat issues. (#9938 and all that.) A performance-enlightened user would not want to materialize all the bytes if they only need some.

[...] but that sounds like something for us to do internally within __eq__(), not something to leave for users to find the most optimal route themselves.

That's one UC for __eq__ (that currently isn't a thing, mind, though #9938 would help (if not optimize) perf there) but I foresee UCs for otherwise comparing, indexing, linearly slicing, etc. image data without fully materializing into a tuple?

@akx
akx force-pushed the image-getdata-linear-access branch from 5896568 to fb6f764 Compare September 14, 2026 16:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants